import pika
import json
import shutil
import os
import multiprocessing
import sys
import traceback
from .load_config import config
from .mqRPC_tools import logger
from .mqRPC_deadMsg_consumer import MqRPCDeadMsgConsumer


def get_rpc_server(server_queue, durable, timeout=300000, server_dead_msg_consumer=False):
    return ConcreteMqRPCServer(server_queue, durable, timeout, server_dead_msg_consumer)


# 定义MqRPCServer接口
# 依赖倒转
class MqRPCServer:

    def set_current_parent_class(self, name):
        pass

    def register(self, in_class=False, point=None, parent_class_name=None):
        pass

    def generate_client(self, folder_path):
        pass

    def start(self):
        pass


class ConcreteMqRPCServer(MqRPCServer):

    def __init__(self, server_queue, durable, timeout=300000, server_dead_msg_consumer=False):
        # server_name : 将作为RPC queue的名称
        # timeout : 默认超时时间(ms) None为不超时(不可为零)
        # 默认超时时间为5min
        # 注意该timeout  对在消息队列中的未确认消息不生效
        # 只能解决消息在queue中等待时间超过timeout的数据

        self.server_queue = server_queue
        self.dead_queue = self.server_queue + "_dead_queue"
        self.durable = durable
        self.timeout = timeout

        # 服务端死信消费者
        # 默认为关闭服务端 开启客户端的
        self.server_dead_msg_consumer = server_dead_msg_consumer

        # 所有的过程函数注册到这里
        self.processes = {}

        # 这个是为了生成客户端定义的
        self.current_parent_class_name = "DefaultClient"

        # 连接凭证
        self.credentials = pika.PlainCredentials(config["rabbitmq_user"], config["rabbitmq_pwd"])
        self.connect_paras = pika.ConnectionParameters(host=config["rabbitmq_host"], port=config.getint("rabbitmq_port")
                                                       , credentials=self.credentials,
                                                       virtual_host=config["rabbitmq_vhost"])

    def set_current_parent_class(self, name):
        self.current_parent_class_name = name

    def __connect(self):
        # 连接mq
        self.connection = pika.BlockingConnection(self.connect_paras)
        self.channel = self.connection.channel()

        # 声明死信队列 这个是为了实现超时
        self.channel.queue_declare(queue=self.dead_queue, durable=self.durable)

        # 队列参数 死信队列超时时间等
        arguments = {"x-dead-letter-exchange": "", "x-dead-letter-routing-key": self.dead_queue}
        if self.timeout and self.timeout > 0:
            arguments["x-message-ttl"] = self.timeout

        # 声明RPC队列
        self.channel.queue_declare(queue=self.server_queue, durable=self.durable,
                                   arguments=arguments)

        # 定义回调函数和负载均衡(如果启动了多个server)相关的参数
        self.channel.basic_qos(prefetch_count=1)
        self.channel.basic_consume(queue=self.server_queue, consumer_callback=self.on_request, no_ack=False)

    def on_request(self, ch, method, props, body):
        # consume 回调函数

        # 返回值 格式 {status:,data:,reason:, exception:}
        # status: 100 timeout,404 未找到该RPC过程,200 成功执行, 500 request消息格式错误， 600 请求的该RPC参数错误, 700 其他错误
        response = {"status": "404", "reason": "", "data": None, "exception": ""}
        request = None
        func_point = "Unknown"
        class_point = "Unknown"
        try:
            # request格式{"class_point":,"func_point":,"paras": {"args":,"kwargs":}}
            request = json.loads(str(body, encoding='utf8'))
            if "class_point" not in request or "func_point" not in request:
                logger.warning("请求格式出错 没有func_point或class_point键值对 "
                                + "corr_id=" + props.correlation_id + ",reply_to=" + props.reply_to)
                response = {"status": "500"}
            else:
                func_point = request["func_point"]
                class_point = request["class_point"]
                if class_point not in self.processes:
                    logger.warning("没有找到名为" + request["func_point"] + "的RPC过程类 "
                                   + "corr_id=" + props.correlation_id + ",reply_to=" + props.reply_to)
                    response = {"status": "404"}
                elif func_point not in self.processes[class_point]:
                    logger.warning("在类"+class_point+"中没有找到名为"+ request["func_point"]+"的RPC过程 "
                                   + "corr_id=" + props.correlation_id + ",reply_to=" + props.reply_to)
                    response = {"status": "404"}
                else:
                    logger.debug("过程" + class_point + "." + func_point + " 参数; args: " + str(request["paras"]["args"])
                                 + " kwargs: " + str(request["paras"]["kwargs"]))
                    # 执行过程
                    response["data"] = self.processes[class_point][func_point](*request["paras"]["args"], **request["paras"]["kwargs"])
                    response["status"] = "200"

        except json.decoder.JSONDecodeError as e:
            # json 解码异常
            logger.warning("json解码出错 700 "+"corr_id="+props.correlation_id+",reply_to="+props.reply_to)
            response["status"] = "700"
            response["reason"] = "json解码出错"
            response["exception"] = type(e).__name__
        except TypeError as e:
            exce_str = str(e) + " at line " + str(sys.exc_info()[2].tb_lineno)
            logger.warning("过程"+request["func_point"]+"参数错误 600 " + exce_str + "corr_id=" + props.correlation_id + ",reply_to=" + props.reply_to)
            response["status"] = "600"
            response["reason"] = exce_str
            response["exception"] = type(e).__name__
            # 打印异常的位置
            traceback.print_exc()
        except Exception as e:
            exce_str = str(sys.exc_info()) + " at line " + str(sys.exc_info()[2].tb_lineno)
            logger.warning("执行过程函数"+class_point+"."+func_point+"出现异常:" + exce_str + " corr_id=" + props.correlation_id + ",reply_to=" + props.reply_to)
            response["status"] = "700"
            response["reason"] = exce_str
            response["exception"] = type(e).__name__
            # 打印异常的位置
            traceback.print_exc()

        ch.basic_publish(exchange='',
                         routing_key=props.reply_to,
                         properties=pika.BasicProperties(correlation_id=props.correlation_id),
                         body=json.dumps(response))
        # print("************************************************")
        logger.info("request OK reply to " + props.reply_to)
        ch.basic_ack(delivery_tag=method.delivery_tag)

    def register(self, in_class=False, point=None, parent_class_name=None):
        # 注册装饰器 装饰需要暴露的的RPC接口
        def decorator(func):
            # 函数名
            if point:
                _point = point
            else:
                _point = func.__name__
            # 函数在客户端类的name
            if not in_class:
                if parent_class_name:
                    class_name = parent_class_name
                else:
                    class_name = self.current_parent_class_name
            else:
                # 如果在类中 也就是方法
                class_name = func.__qualname__.split(".")[-2]
            if class_name not in self.processes:
                self.processes[class_name] = {}
            # 存放到processes
            self.processes[class_name][_point] = func
            return func

        return decorator

    def generate_client(self, folder_path):
        template = \
"""from .mqRPC_client import MqRPCClient
import sys


class %(class_name)s(MqRPCClient):
    def __init__(self, async=False):
        super().__init__(async)
        self.server_queue = "%(server_queue)s"
        self.dead_queue = "%(dead_queue)s"
"""

        func_template = \
"""
    def %(func_name)s(self, *args, **kwargs):
        request = {'class_point': '%(class_point)s', 'func_point': '%(func_point)s', 
                   'paras': {'args': args, 'kwargs': kwargs}}
        func_name = sys._getframe().f_code.co_name
        return self.send_request(request, func_name)
"""
        dst_folder = os.path.dirname(__file__)
        # 依赖文件列表
        depends = ["mqRPC_client.py", "mqRPC_exception.py", "load_config.py", "mqRPC_deadMsg_consumer.py", "conf"]
        for file in depends:
            # 拷贝依赖文件
            shutil.copy(dst_folder + "/"+file, folder_path)
        # 创建__init__.py
        with open(folder_path + "/__init__.py", "w") as f:
            for class_name in self.processes:
                f.write("from ." + class_name + " import " + class_name + "\n")
            # 初始化MqRPCClient静态变量
            f.write("from .mqRPC_client import MqRPCClient\n")
            f.write("MqRPCClient.server_queue = '%s'\n" % self.server_queue)
            f.write("MqRPCClient.dead_queue = '%s'\n" % self.dead_queue)
        # 根据已经注册的函数生成client的python文件
        for class_name in self.processes:
            # 创建相应类的client文件
            with open(folder_path+"/"+class_name+".py", "w") as f:
                # 写入通用的部分
                f.write(template % {"class_name": class_name,"server_queue": self.server_queue, "dead_queue": self.dead_queue})
                # 写入类方法
                for func_name in self.processes[class_name]:
                    f.write(func_template % {"class_point": class_name, "func_point": func_name,
                                             "func_name": func_name})

    def _start_dead_msg_consumer(self):
        dead_consumer = MqRPCDeadMsgConsumer(self.dead_queue, self.durable)
        dead_consumer.start_consume()

    def start(self):
        if self.server_dead_msg_consumer:
            # 新开一个进程运行死信消费者
            dead_consumer_p = multiprocessing.Process(target=self._start_dead_msg_consumer)
            dead_consumer_p.start()
        # 连接以及预先操作
        self.__connect()
        # 开始消费 RPC请求
        logger.debug("开始消费，等待请求")
        self.channel.start_consuming()
